package com.cougil.uoc.seghismed.cipher;

import iaik.pkcs.PKCSException;
import iaik.pkcs.pkcs12.CertificateBag;
import iaik.pkcs.pkcs12.KeyBag;
import iaik.pkcs.pkcs12.PKCS12;
import iaik.x509.X509Certificate;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.security.PrivateKey;
import java.security.PublicKey;

import org.apache.log4j.Logger;

import com.cougil.uoc.seghismed.utils.SeghismedException;

/**
 * Classe que permet encapsular la lògica encarregada de gestionar la informació contenida
 * en els contenidor de tipus PKCS#12
 * 
 * @author Ignacio Cougil Jares
 * @author <a href="mailto:jcastellar@uoc.edu">Jordi Castella-Roca</a>
 */
public class P12 {

	private static final Logger logger = Logger.getLogger(P12.class);

	private X509Certificate certificate;
	private X509Certificate[] chain;
	private PrivateKey privateKey;
	private PublicKey publicKey;

	private P12() {
		super();
	}

	/**
	 * Crea una nova instància encarregada de gestionar la informació contenida en el
	 * contenidor de tipus PKCS#12 que es carregarà amb el nom i contrasenya informats
	 * 
	 * @param fileName
	 *            Nom del contenidor PKCS#12 que es preten carregar
	 * @param password
	 *            Paraula clau per al contenidor PKCS#12 que es preten carregar
	 * @exception SeghismedException
	 *                if an error occurs
	 */
	public P12(String fileName, String password) throws SeghismedException {
		this();
		try {
			String friendlyName;
			byte[] localKeyID;
			String slocalKeyID;

			// SeghismedUtils.showClassAbsolutePath(this.getClass());
			URL urlResource = this.getClass().getResource(fileName);
			// truc per aconseguir obtenir els fitxers si aquests estan referenciats de forma absoluta en el fitxer de recursos
			if (urlResource == null) {
				urlResource = new URL(fileName);
			}

			P12.logger.info("Loading: '" +urlResource+"'");
			P12.logger.debug(" with password '"+password+"'");

			FileInputStream fileInput = new FileInputStream(urlResource.getFile());

			PKCS12 pkcs12 = new PKCS12(fileInput);

			// Verifiquem la integritat, es a dir, calculem el MAC
			pkcs12.verify(password.toCharArray());

			// Desxifrem el PKCS12
			pkcs12.decrypt(password.toCharArray());

			// Private Key
			KeyBag keyBag = pkcs12.getKeyBag();
			this.privateKey = keyBag.getPrivateKey();

			// Public Key
			// El locakKeyID ens vincula la clau privada amb la publica.
			// Obtenim el localKeyID de la clau privada.
			// A continuacio cerquem a la llista de certificats el certificat que te
			// el mateix localKeyID.
			friendlyName = keyBag.getFriendlyName();
			localKeyID = keyBag.getLocalKeyID();
			slocalKeyID = new String(localKeyID);

			CertificateBag[] certificateBag = pkcs12.getCertificateBags();

			this.chain = CertificateBag.getCertificates(certificateBag);

			setPublicKey(certificateBag, friendlyName, slocalKeyID);

			P12.logger.info(fileName + " loaded");
		} catch (PKCSException e) {
			// no podem mostrar cap error al log perquè encara no està instanciat
			// l'objecte
			P12.logger.error("Error instanciant l'objecte P12",e);
			throw new SeghismedException(e);
		} catch (IOException e) {
			throw new SeghismedException(e);
		}
	}

	/*
	 * // Amb JCE 1.6 - funciona però després no existeix la classe SignedAndEnvelopedData
	 * (veure SignerManager)
	 * 
	 * String friendlyName; byte[] localKeyID; String slocalKeyID;
	 * 
	 * KeyStore ks; try { ks = KeyStore.getInstance("PKCS12");
	 * 
	 * URL urlResource = this.getClass().getResource(...); FileInputStream fis = new
	 * FileInputStream(fileName);
	 * 
	 * logger.debug("Intentant carregar el keystore '"+fileName+"' amb la contrasenya
	 * '"+password+"'"); ks.load(fis, password.toCharArray());
	 * 
	 * logger.debug("Keystore carregat satisfactoriament"); String alias = null; for
	 * (Enumeration<String> e = ks.aliases() ; e.hasMoreElements() ;) { alias =
	 * e.nextElement(); logger.debug("alias: "+alias); }
	 * 
	 * privateKey = (PrivateKey)ks.getKey(alias, password.toCharArray()); Certificate[]
	 * chainCert = ks.getCertificateChain(alias);
	 * 
	 * CertificateFactory cf = CertificateFactory.getInstance("X.509");
	 * 
	 * chain = new X509Certificate[chainCert.length];
	 * 
	 * for (int i = 0; i < chainCert.length; i++) { ByteArrayInputStream bais = new
	 * ByteArrayInputStream(chainCert[i].getEncoded()); Collection c =
	 * cf.generateCertificates(bais); chain[i] = (X509Certificate) c.iterator().next();
	 * showCertInfo(chain[i]); }
	 * 
	 * setPublicKey(alias);
	 *  } catch (KeyStoreException e1) { e1.printStackTrace(); } catch (IOException e) {
	 * logger.error("Error: "+e.fillInStackTrace()); return; } catch (Exception e) {
	 * e.printStackTrace(); }
	 * 
	 * public static void showCertInfo(Certificate cert) { System.out.println("--info
	 * certificate start--"); if (cert instanceof X509Certificate) { X509Certificate
	 * x509cert = (X509Certificate)cert;
	 *  // Subjecte Principal principal = x509cert.getSubjectDN(); String subjectDn =
	 * principal.getName();
	 * 
	 * System.out.println("S/N: "+x509cert.getSerialNumber());
	 * 
	 * System.out.println("Subjecte: "+subjectDn);
	 *  // Emisor principal = x509cert.getIssuerDN(); String issuerDn =
	 * principal.getName();
	 * 
	 * System.out.println("Emisor: "+issuerDn); } System.out.println("--info certificate
	 * end--"); }
	 * 
	 * private void setPublicKey(String alias) { for (int i = 0; i < chain.length; i++) {
	 * String subjectName = chain[i].getSubjectDN().getName(); if
	 * (subjectName.equalsIgnoreCase(alias)) { certificate = chain[i]; logger.debug("ID: " +
	 * certificate.getSerialNumber() + " -/- " + subjectName); break; } } publicKey =
	 * certificate.getPublicKey(); }
	 */

	/**
	 * Describe <code>getCertificate</code> method here.
	 *
	 * @return a <code>X509Certificate</code> value
	 */
	public X509Certificate getCertificate() {
		return this.certificate;
	}

	/**
	 * Describe <code>getCertificates</code> method here.
	 *
	 * @return a <code>X509Certificate[]</code> value
	 */
	public X509Certificate[] getCertificates() {
		return this.chain;
	}

	/**
	 * Describe <code>getPrivateKey</code> method here.
	 *
	 * @return a <code>PrivateKey</code> value
	 */
	public PrivateKey getPrivateKey() {
		return this.privateKey;
	}

	/**
	 * Describe <code>getPublicKey</code> method here.
	 *
	 * @return a <code>PublicKey</code> value
	 */
	public PublicKey getPublicKey() {
		return this.publicKey;
	}

	// Busca el certificat amb el slocalKeyID indicat com a parametre.
	// El friendlyName es de propina, per imprimir-lo.
	private void setPublicKey(CertificateBag[] certificateBag,
			String friendlyName, String slocalKeyID) {
		int i;
		String friendlyNameCRT;
		byte[] localKeyIDCRT;
		String slocalKeyIDCRT;

		for (i = 0; i < certificateBag.length; i++) {
			friendlyNameCRT = (certificateBag[i]).getFriendlyName();
			localKeyIDCRT = (certificateBag[i]).getLocalKeyID();
			slocalKeyIDCRT = new String(localKeyIDCRT);
			if (slocalKeyIDCRT.equals(slocalKeyID)) {
				this.certificate = certificateBag[i].getCertificate();
				P12.logger.info("ID: " + friendlyNameCRT + " -/- " + friendlyName);
				break;
			}
		}

		this.publicKey = this.certificate.getPublicKey();
	}

}
